#!/usr/bin/env bash
export LC_ALL=C

if ((BASH_VERSINFO[0] < 4)); then
  echo "Sorry, you need bash 4.0 or newer to run this script."
  exit 1
fi

function ignore_msrs_always() {
  # Make sure the host has /etc/modprobe.d
  if [ -d /etc/modprobe.d ]; then
    # Skip if ignore_msrs is already enabled, assumes initramfs has been rebuilt
    if grep -lq 'ignore_msrs=Y' /etc/modprobe.d/kvm-quickemu.conf >/dev/null 2>&1; then
      echo "options kvm ignore_msrs=Y" | sudo tee /etc/modprobe.d/kvm-quickemu.conf
      sudo update-initramfs -k all -u
    fi
  else
    echo "ERROR! /etc/modprobe.d was not found, I don't know how to configure this system."
    exit 1
  fi
}

function ignore_msrs_alert() {
  local ignore_msrs=""
  if [ -e /sys/module/kvm/parameters/ignore_msrs ]; then
    ignore_msrs=$(cat /sys/module/kvm/parameters/ignore_msrs)
    if [ "${ignore_msrs}" == "N" ]; then
      echo " - MSR:      WARNING! Ignoring unhandled Model-Specific Registers is disabled."
      echo
      echo "             echo 1 | sudo tee /sys/module/kvm/parameters/ignore_msrs"
      echo
      echo "             If you are unable to run macOS or Windows VMs then run the above 👆"
      echo "             This will enable ignoring of unhandled MSRs until you reboot the host."
      echo "             You can make this change permenant by running: 'quickemu --ignore-msrs-always'"
    fi
  fi
}

function delete_shortcut() {
  local SHORTCUT_DIR="${HOME}/.local/share/applications/"
  if [ -e "${SHORTCUT_DIR}/${VMNAME}.desktop" ]; then
    rm "${SHORTCUT_DIR}/${VMNAME}.desktop"
    echo "Deleted ${VM} desktop shortcut"
  fi
}

function delete_disk() {
  if [ -e "${disk_img}" ]; then
    rm "${disk_img}"
    # Remove any EFI vars, but not for macOS
    rm "${VMDIR}"/OVMF_VARS*.fd >/dev/null 2>&1
    rm "${VMPATH}/${VMDIR}"/OVMF_VARS*.fd >/dev/null 2>&1
    rm "${VMDIR}/${VMNAME}-vars.fd" >/dev/null 2>&1
    rm "${VMPATH}/${VMDIR}/${VMNAME}-vars.fd" > /dev/null 2>&1
    echo "SUCCESS! Deleted ${disk_img}"
    delete_shortcut
  else
    echo "NOTE! ${disk_img} not found. Doing nothing."
  fi
}

function delete_vm() {
  if [ -d "${VMDIR}" ]; then
    rm -rf "${VMDIR}"
    rm "${VM}"
    echo "SUCCESS! Deleted ${VM} and ${VMDIR}"
    delete_shortcut
  else
    echo "NOTE! ${VMDIR} not found. Doing nothing."
  fi
}

function snapshot_apply() {
  local TAG="${1}"
  if [ -z "${TAG}" ]; then
    echo "ERROR! No snapshot tag provided."
    exit
  fi

  if [ -e "${disk_img}" ]; then
    if ${QEMU_IMG} snapshot -q -a "${TAG}" "${disk_img}"; then
      echo "SUCCESS! Applied snapshot ${TAG} to ${disk_img}"
    else
      echo "ERROR! Failed to apply snapshot ${TAG} to ${disk_img}"
    fi
  else
    echo "NOTE! ${disk_img} not found. Doing nothing."
  fi
}

function snapshot_create() {
  local TAG="${1}"
  if [ -z "${TAG}" ]; then
    echo "ERROR! No snapshot tag provided."
    exit
  fi

  if [ -e "${disk_img}" ]; then
    if ${QEMU_IMG} snapshot -q -c "${TAG}" "${disk_img}"; then
      echo "SUCCESS! Created snapshot ${TAG} of ${disk_img}"
    else
      echo "ERROR! Failed to create snapshot ${TAG} of ${disk_img}"
    fi
  else
    echo "NOTE! ${disk_img} not found. Doing nothing."
  fi
}

function snapshot_delete() {
  local TAG="${1}"
  if [ -z "${TAG}" ]; then
    echo "ERROR! No snapshot tag provided."
    exit
  fi

  if [ -e "${disk_img}" ]; then
    if ${QEMU_IMG} snapshot -q -d "${TAG}" "${disk_img}"; then
      echo "SUCCESS! Deleted snapshot ${TAG} of ${disk_img}"
    else
      echo "ERROR! Failed to delete snapshot ${TAG} of ${disk_img}"
    fi
  else
    echo "NOTE! ${disk_img} not found. Doing nothing."
  fi
}

function snapshot_info() {
  if [ -e "${disk_img}" ]; then
    ${QEMU_IMG} info "${disk_img}"
  fi
}

function get_port() {
    local PORT_START=$1
    local PORT_RANGE=$((PORT_START+$2))
    local PORT
    for ((PORT = PORT_START; PORT <= PORT_RANGE; PORT++)); do
        # Make sure port scans do not block too long.
        timeout 0.1s bash -c "echo >/dev/tcp/127.0.0.1/${PORT}" >/dev/null 2>&1
        if [ ${?} -eq 1 ]; then
            echo "${PORT}"
            break
        fi
    done
}

function enable_usb_passthrough() {
  local DEVICE=""
  local USB_BUS=""
  local USB_DEV=""
  local USB_NAME=""
  local VENDOR_ID=""
  local PRODUCT_ID=""
  local USB_NOT_READY=0

  # Have any USB devices been requested for pass-through?
  if (( ${#usb_devices[@]} )); then
    echo " - USB:      Host pass-through requested:"
    for DEVICE in "${usb_devices[@]}"; do
      VENDOR_ID=$(echo "${DEVICE}" | cut -d':' -f1)
      PRODUCT_ID=$(echo "${DEVICE}" | cut -d':' -f2)
      USB_BUS=$(lsusb -d "${VENDOR_ID}:${PRODUCT_ID}" | cut -d' ' -f2)
      USB_DEV=$(lsusb -d "${VENDOR_ID}:${PRODUCT_ID}" | cut -d' ' -f4 | cut -d':' -f1)
      USB_NAME=$(lsusb -d "${VENDOR_ID}:${PRODUCT_ID}" | cut -d' ' -f7-)
      if [ -w "/dev/bus/usb/${USB_BUS}/${USB_DEV}" ]; then
        echo "             o ${USB_NAME} on bus ${USB_BUS} device ${USB_DEV} is accessible."
      else
        echo "             x ${USB_NAME} on bus ${USB_BUS} device ${USB_DEV} needs permission changes:"
        echo "               sudo chown -v root:${USER} /dev/bus/usb/${USB_BUS}/${USB_DEV}"
        USB_NOT_READY=1
      fi
      USB_PASSTHROUGH="${USB_PASSTHROUGH} -device usb-host,bus=hostpass.0,vendorid=0x${VENDOR_ID},productid=0x${PRODUCT_ID}"
    done

    if [ "${USB_NOT_READY}" -eq 1 ]; then
        echo "               ERROR! USB permission changes are required 👆"
      exit 1
    fi
  fi
}

function check_cpu_flag() {
  local HOST_CPU_FLAG="${1}"
  if lscpu | grep -o "^Flags\b.*: .*\b${HOST_CPU_FLAG}\b" > /dev/null; then
    return 0
  else
    return 1
  fi
}

function efi_vars() {
  local VARS_IN=""
  local VARS_OUT=""
  VARS_IN="${1}"
  VARS_OUT="${2}"

  if [ ! -e "${VARS_OUT}" ]; then
    if [ -e "${VARS_IN}" ]; then
      cp "${VARS_IN}" "${VARS_OUT}"
    else
      echo "ERROR! ${VARS_IN} was not found. Please install edk2."
      exit 1
    fi
  fi
}

function vm_boot() {
  local AUDIO_DEV=""
  local BALLOON="-device virtio-balloon"
  local BOOT_STATUS=""
  local CPU=""
  local DISK_USED=""
  local DISPLAY_DEVICE=""
  local DISPLAY_RENDER=""
  local EFI_CODE=""
  local EFI_VARS=""
  local GUEST_CPU_CORES=""
  local GUEST_CPU_LOGICAL_CORES=""
  local GUEST_CPU_THREADS=""
  local HOST_CPU_CORES=""
  local HOST_CPU_SMT=""
  local HOST_CPU_SOCKETS=""
  local HOST_CPU_VENDOR=""
  local GUEST_TWEAKS=""
  local KERNEL_NAME="Unknown"
  local KERNEL_NODE=""
  local KERNEL_VER="?"
  local LSB_DESCRIPTION="Unknown OS"
  local MACHINE_TYPE="q35"
  local MAC_BOOTLOADER=""
  local MAC_MISSING=""
  local MAC_DISK_DEV="ide-hd,bus=ahci.2"
  local NET_DEVICE="virtio-net"
  local OSK=""
  local SMM="off"
  local USB_HOST_PASSTHROUGH_CONTROLLER="qemu-xhci"
  local VIDEO=""

  KERNEL_NAME=$(uname --kernel-name)
  KERNEL_NODE="($(uname --nodename))"
  KERNEL_VER=$(uname --kernel-release | cut -d'.' -f1-2)

  if command -v lsb_release &>/dev/null; then
    LSB_DESCRIPTION=$(lsb_release --description --short)
  elif [ -e /etc/os-release ]; then
    LSB_DESCRIPTION=$(grep PRETTY_NAME /etc/os-release | cut -d'"' -f2)
  fi

  echo "Quickemu ${VERSION} using ${QEMU} v${QEMU_VER_LONG}"
  echo " - Host:     ${LSB_DESCRIPTION} running ${KERNEL_NAME} ${KERNEL_VER} ${KERNEL_NODE}"

  HOST_CPU_CORES=$(nproc --all)
  HOST_CPU_MODEL=$(lscpu | grep '^Model name:' | cut -d':' -f2 | sed 's/  //g')
  HOST_CPU_SOCKETS=$(lscpu | grep -E 'Socket' | cut -d':' -f2 | sed 's/ //g')
  HOST_CPU_VENDOR=$(lscpu | grep -E 'Vendor' | cut -d':' -f2 | sed 's/ //g')

  # A CPU with Intel VT-x / AMD SVM support is required
  if [ "${HOST_CPU_VENDOR}" == "GenuineIntel" ]; then
    if ! check_cpu_flag vmx; then
      echo "ERROR! Intel VT-x support is required."
      exit 1
    fi
  elif [ "${HOST_CPU_VENDOR}" == "AuthenticAMD" ]; then
    if ! check_cpu_flag svm; then
      echo "ERROR! AMD SVM support is required."
      exit 1
    fi
  fi

  if [ -z "${cpu_cores}" ]; then
      if [ "${HOST_CPU_CORES}" -ge 32 ]; then
        GUEST_CPU_CORES="16"
      elif [ "${HOST_CPU_CORES}" -ge 16 ]; then
        GUEST_CPU_CORES="8"
      elif [ "${HOST_CPU_CORES}" -ge 8 ]; then
        GUEST_CPU_CORES="4"
      elif [ "${HOST_CPU_CORES}" -ge 4 ]; then
        GUEST_CPU_CORES="2"
      else
        GUEST_CPU_CORES="1"
      fi
  else
      GUEST_CPU_CORES="${cpu_cores}"
  fi

  # Account for Hyperthreading/SMT.
  if [ -e /sys/devices/system/cpu/smt/control ] && [ "${GUEST_CPU_CORES}" -ge 2 ]; then
    HOST_CPU_SMT=$(cat /sys/devices/system/cpu/smt/control)
    case ${HOST_CPU_SMT} in
      on)
        GUEST_CPU_THREADS=2
        GUEST_CPU_LOGICAL_CORES=$(( GUEST_CPU_CORES / GUEST_CPU_THREADS ))
        ;;
      *)
        GUEST_CPU_THREADS=1
        GUEST_CPU_LOGICAL_CORES=${GUEST_CPU_CORES}
        ;;
    esac
  else
    GUEST_CPU_THREADS=1
    GUEST_CPU_LOGICAL_CORES=${GUEST_CPU_CORES}
  fi

  local SMP="-smp cores=${GUEST_CPU_LOGICAL_CORES},threads=${GUEST_CPU_THREADS},sockets=${HOST_CPU_SOCKETS}"
  echo " - CPU:      ${HOST_CPU_MODEL}"
  echo -n " - CPU VM:   ${HOST_CPU_SOCKETS} Socket(s), ${GUEST_CPU_LOGICAL_CORES} Core(s), ${GUEST_CPU_THREADS} Thread(s)"

  local RAM_VM="2G"
  if [ -z "${ram}" ]; then
      local RAM_HOST=""
      RAM_HOST=$(free --mega -h | grep Mem | cut -d':' -f2 | cut -d'G' -f1 | sed 's/ //g')
      #Round up - https://github.com/wimpysworld/quickemu/issues/11
      RAM_HOST=$(printf '%.*f\n' 0 "${RAM_HOST}")
      if [ "${RAM_HOST}" -ge 128 ]; then
        RAM_VM="32G"
      elif [ "${RAM_HOST}" -ge 64 ]; then
        RAM_VM="16G"
      elif [ "${RAM_HOST}" -ge 16 ]; then
        RAM_VM="8G"
      elif [ "${RAM_HOST}" -ge 8 ]; then
        RAM_VM="4G"
      fi
  else
      RAM_VM="${ram}"
  fi
  echo ", ${RAM_VM} RAM"

  if [ "${guest_os}" == "macos" ] || [ "${guest_os}" == "windows" ]; then
    if [ "${RAM_VM//G/}" -lt 4 ]; then
        echo "ERROR! You have insufficient RAM to run ${guest_os} in a VM"
        exit 1
    fi
  fi

  # Force to lowercase.
  boot=${boot,,}
  guest_os=${guest_os,,}

  # Always Boot macOS using EFI
  if [ "${guest_os}" == "macos" ]; then
    boot="efi"
    if [ -e "${VMDIR}/OVMF_CODE.fd" ] && [ -e "${VMDIR}/OVMF_VARS-1024x768.fd" ]; then
      EFI_CODE="${VMDIR}/OVMF_CODE.fd"
      EFI_VARS="${VMDIR}/OVMF_VARS-1024x768.fd"
    else
      MAC_MISSING="Firmware"
    fi

    if [ -e "${VMDIR}/OpenCore.qcow2" ]; then
      MAC_BOOTLOADER="${VMDIR}/OpenCore.qcow2"
    elif [ -e "${VMDIR}/ESP.qcow2" ]; then
      # Backwards compatibility for Clover
      MAC_BOOTLOADER="${VMDIR}/ESP.qcow2"
    else
      MAC_MISSING="Bootloader"
    fi

    if [ -n "${MAC_MISSING}" ]; then
      echo "ERROR! macOS ${MAC_MISSING} was not found."
      echo "       Use 'quickget' to download the required files."
      exit 1
    fi
    BOOT_STATUS="EFI (macOS), OVMF ($(basename "${EFI_CODE}")), SecureBoot (${secureboot})."
  elif [[ "${boot}" == *"efi"* ]]; then
    EFI_VARS="${VMDIR}/OVMF_VARS.fd"

    # Preserve backward compatibility
    if [ -e "${VMDIR}/${VMNAME}-vars.fd" ]; then
      mv "${VMDIR}/${VMNAME}-vars.fd" "${EFI_VARS}"
    elif [ -e "${VMDIR}/OVMF_VARS_4M.fd" ]; then
      mv "${VMDIR}/OVMF_VARS_4M.fd" "${EFI_VARS}"
    fi

    # OVMF_CODE_4M.fd is for booting guests in non-Secure Boot mode.
    # While this image technically supports Secure Boot, it does so
    # without requiring SMM support from QEMU

    # OVMF_CODE.secboot.fd is like OVMF_CODE_4M.fd, but will abort if QEMU
    # does not support SMM.

    # https://bugzilla.redhat.com/show_bug.cgi?id=1929357#c5
    case ${secureboot} in
      on)
         if [ -e "/usr/share/OVMF/OVMF_CODE_4M.secboot.fd" ]; then
          EFI_CODE="/usr/share/OVMF/OVMF_CODE_4M.secboot.fd"
          efi_vars "/usr/share/OVMF/OVMF_VARS_4M.fd" "${EFI_VARS}"
        elif [ -e "/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd" ]; then
          EFI_CODE="/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd"
          efi_vars "/usr/share/edk2/ovmf/OVMF_VARS.fd" "${EFI_VARS}"
        elif [ -e "/usr/share/OVMF/x64/OVMF_CODE.secboot.fd" ]; then
          EFI_CODE="/usr/share/OVMF/x64/OVMF_CODE.secboot.fd"
          efi_vars "/usr/share/OVMF/x64/OVMF_VARS.fd" "${EFI_VARS}"
        elif [ -e "/usr/share/edk2-ovmf/OVMF_CODE.secboot.fd" ]; then
	        EFI_CODE="/usr/share/edk2-ovmf/OVMF_CODE.secboot.fd"
	        efi_vars "/usr/share/edk2-ovmf/OVMF_VARS.fd" "${EFI_VARS}"
        elif [ -e "/usr/share/qemu/ovmf-x86_64-smm-ms-code.bin" ]; then
          EFI_CODE="/usr/share/qemu/ovmf-x86_64-smm-ms-code.bin"
          efi_vars "/usr/share/qemu/ovmf-x86_64-smm-ms-vars.bin" "${EFI_VARS}"
        elif [ -e "/usr/share/qemu/edk2-x86_64-secure-code.fd" ]; then
          EFI_CODE="/usr/share/qemu/edk2-x86_64-secure-code.fd"
          efi_vars "/usr/share/qemu/edk2-x86_64-code.fd" "${EFI_VARS}"
        # Solus new place to hide
        elif [ -e "/usr/share/edk2-ovmf/x64/OVMF_CODE.secboot.fd" ]; then
          EFI_CODE="/usr/share/edk2-ovmf/x64/OVMF_CODE.secboot.fd"
          efi_vars "/usr/share/edk2-ovmf/x64/OVMF_VARS.fd" "${EFI_VARS}"
        else
          echo "ERROR! SecureBoot was requested but no SecureBoot capable firmware was found."
          echo "       Please install OVMF firmware."
          exit 1
        fi
        ;;
      *)
        if [ -e "/usr/share/OVMF/OVMF_CODE_4M.fd" ]; then
          EFI_CODE="/usr/share/OVMF/OVMF_CODE_4M.fd"
          efi_vars "/usr/share/OVMF/OVMF_VARS_4M.fd" "${EFI_VARS}"
        elif [ -e "/usr/share/edk2/ovmf/OVMF_CODE.fd" ]; then
          EFI_CODE="/usr/share/edk2/ovmf/OVMF_CODE.fd"
          efi_vars "/usr/share/edk2/ovmf/OVMF_VARS.fd" "${EFI_VARS}"
        elif [ -e "/usr/share/OVMF/OVMF_CODE.fd" ]; then
          EFI_CODE="/usr/share/OVMF/OVMF_CODE.fd"
          efi_vars "/usr/share/OVMF/OVMF_VARS.fd" "${EFI_VARS}"
        elif [ -e "/usr/share/OVMF/x64/OVMF_CODE.fd" ]; then
          EFI_CODE="/usr/share/OVMF/x64/OVMF_CODE.fd"
          efi_vars "/usr/share/OVMF/x64/OVMF_VARS.fd" "${EFI_VARS}"
        elif [ -e "/usr/share/edk2-ovmf/OVMF_CODE.fd" ]; then
          EFI_CODE="/usr/share/edk2-ovmf/OVMF_CODE.fd"
          efi_vars "/usr/share/edk2-ovmf/OVMF_VARS.fd" "${EFI_VARS}"
        elif [ -e "/usr/share/qemu/ovmf-x86_64-4m-code.bin" ]; then
          EFI_CODE="/usr/share/qemu/ovmf-x86_64-4m-code.bin"
          efi_vars "/usr/share/qemu/ovmf-x86_64-4m-vars.bin" "${EFI_VARS}"
        elif [ -e "/usr/share/qemu/edk2-x86_64-code.fd" ]; then
          EFI_CODE="/usr/share/qemu/edk2-x86_64-code.fd"
          efi_vars "/usr/share/qemu/edk2-x86_64-code.fd" "${EFI_VARS}"
        # Solus
        elif [ -e "/usr/share/edk2-ovmf/x64/OVMF_CODE.fd" ]; then
          EFI_CODE="/usr/share/edk2-ovmf/x64/OVMF_CODE.fd"
          efi_vars "/usr/share/edk2-ovmf/x64/OVMF_VARS.fd" "${EFI_VARS}"
        else
          echo "ERROR! EFI boot requested but no EFI firmware found."
          echo "       Please install OVMF firmware."
          exit 1
        fi
        ;;
    esac

    # Make sure EFI_VARS references an actual, writeable, file
    if [ ! -f "${EFI_VARS}" ] || [ ! -w "${EFI_VARS}" ]; then
      echo " - EFI:      ERROR! ${EFI_VARS} is not a regular file or not writeable."
      echo "             Deleting ${EFI_VARS}. Please re-run quickemu."
      rm -f "${EFI_VARS}"
      exit 1
    fi

    # If EFI_CODE references a symlink, resolve it to the real file.
    if [ -L "${EFI_CODE}" ]; then
      echo " - EFI:      WARNING! ${EFI_CODE} is a symlink."
      echo -n "             Resolving to... "
      EFI_CODE=$(realpath "${EFI_CODE}")
      echo "${EFI_CODE}"
    fi
    BOOT_STATUS="EFI (${guest_os^}), OVMF (${EFI_CODE}), SecureBoot (${secureboot})."
  else
    BOOT_STATUS="Legacy BIOS (${guest_os^})"
    boot="legacy"
    secureboot="off"
  fi

  echo " - BOOT:     ${BOOT_STATUS}"

  # Make any OS specific adjustments
  case ${guest_os} in
    *bsd|haiku|freedos|linux)
      CPU="-cpu host,kvm=on"
      if [ "${HOST_CPU_VENDOR}" == "AuthenticAMD" ]; then
        CPU="${CPU},topoext"
      fi

      if [ "${guest_os}" == "freebsd" ] || [ "${guest_os}" == "ghostbsd" ]; then
        MOUSE="usb"
      elif [ "${guest_os}" == "haiku" ] || [ "${guest_os}" == "freedos" ]; then
        MACHINE_TYPE="pc"
        NET_DEVICE="rtl8139"
      fi

      if [ "${guest_os}" == "freedos" ] ; then
        # fix for #382
        SMM="on"
      fi


      if [ -z "${disk_size}" ]; then
        disk_size="16G"
      fi
      ;;
    batocera)
      CPU="-cpu host,kvm=on"
      if [ "${HOST_CPU_VENDOR}" == "AuthenticAMD" ]; then
        CPU="${CPU},topoext"
      fi
      MACHINE_TYPE="pc"
      NET_DEVICE="rtl8139"
      disk_size="8G"
    ;;
    kolibrios)
      CPU="-cpu qemu32,kvm=on"
      if [ "${HOST_CPU_VENDOR}" == "AuthenticAMD" ]; then
        CPU="${CPU},topoext"
      fi
      MACHINE_TYPE="pc"
      NET_DEVICE="rtl8139"
      ;;
    macos)
      #https://www.nicksherlock.com/2020/06/installing-macos-big-sur-on-proxmox/
      # A CPU with SSE4.1 support is required for >= macOS Sierra
      if check_cpu_flag sse4_1; then
        case ${HOST_CPU_VENDOR} in
          GenuineIntel)
            CPU="-cpu host,kvm=on,vendor=GenuineIntel,+hypervisor,+invtsc,+kvm_pv_eoi,+kvm_pv_unhalt";;
          AuthenticAMD|*)
            # Used in past versions: +movbe,+smep,+xgetbv1,+xsavec,+avx2
            # Warn on AMD:           +fma4,+pcid
            CPU="-cpu Penryn,kvm=on,vendor=GenuineIntel,+aes,+avx,+bmi1,+bmi2,+fma,+hypervisor,+invtsc,+kvm_pv_eoi,+kvm_pv_unhalt,+popcnt,+ssse3,+sse4.2,vmware-cpuid-freq=on,+xsave,+xsaveopt,check";;
        esac
      else
        echo "ERROR! macOS requires a CPU with SSE 4.1 support."
        exit 1
      fi

      OSK=$(echo "bheuneqjbexolgurfrjbeqfthneqrqcyrnfrqbagfgrny(p)NccyrPbzchgreVap" | tr 'A-Za-z' 'N-ZA-Mn-za-m')
      # Disable S3 support in the VM to prevent macOS suspending during install
      GUEST_TWEAKS="-no-hpet -global kvm-pit.lost_tick_policy=discard -global ICH9-LPC.disable_s3=1 -device isa-applesmc,osk=${OSK}"

      # Tune Qemu optimisations based on the macOS release, or fallback to lowest
      # common supported options if none is specified.
      #   * VirtIO Block Media doesn't work in High Sierra (at all) or the Mojave (Recovery Image)
      #   * VirtIO Network is supported since Big Sur
      #   * VirtIO Memory Balloning is supported since Big Sur (https://pmhahn.github.io/virtio-balloon/)
      #   * VirtIO RNG is supported since Big Sur, but exposed to all guests by default.
      case ${macos_release} in
        catalina)
          BALLOON=""
          MAC_DISK_DEV="virtio-blk-pci"
          NET_DEVICE="vmxnet3"
          USB_HOST_PASSTHROUGH_CONTROLLER="usb-ehci"
          ;;
        big-sur|monterey)
          BALLOON="-device virtio-balloon"
          MAC_DISK_DEV="virtio-blk-pci"
          NET_DEVICE="virtio-net"
          USB_HOST_PASSTHROUGH_CONTROLLER="nec-usb-xhci"
          GUEST_TWEAKS="${GUEST_TWEAKS} -global nec-usb-xhci.msi=off"
          ;;
        *)
          # Backwards compatibility if no macos_release is specified.
          # Also safe catch all for High Sierra and Mojave
          BALLOON=""
          MAC_DISK_DEV="ide-hd,bus=ahci.2"
          NET_DEVICE="vmxnet3"
          USB_HOST_PASSTHROUGH_CONTROLLER="usb-ehci"
          ;;
      esac

      if [ -z "${disk_size}" ]; then
        disk_size="96G"
      fi
      ;;
    windows)
      CPU="-cpu host,kvm=on,+hypervisor,+invtsc,l3-cache=on,migratable=no,hv_frequencies,kvm_pv_unhalt,hv_reenlightenment,hv_relaxed,hv_spinlocks=8191,hv_stimer,hv_synic,hv_time,hv_vapic,hv_vendor_id=1234567890ab,hv_vpindex"
      if [ "${HOST_CPU_VENDOR}" == "AuthenticAMD" ]; then
        CPU="${CPU},topoext"
      fi
      GUEST_TWEAKS="-no-hpet -global kvm-pit.lost_tick_policy=discard"

      # Disable S3 support in the VM to ensure Windows can boot with SecureBoot enabled
      #  - https://wiki.archlinux.org/title/QEMU#VM_does_not_boot_when_using_a_Secure_Boot_enabled_OVMF
      GUEST_TWEAKS="${GUEST_TWEAKS} -global ICH9-LPC.disable_s3=1"

      if [ -z "${disk_size}" ]; then
        disk_size="64G"
      fi
      SMM="on"
      ;;
    *)
      CPU="-cpu host,kvm=on"
      NET_DEVICE="rtl8139"
      if [ -z "${disk_size}" ]; then
        disk_size="32G"
      fi
      echo "WARNING! Unrecognised guest OS: ${guest_os}"
      ;;
  esac

  echo " - Disk:     ${disk_img} (${disk_size})"
  if [ ! -f "${disk_img}" ]; then
      # If there is no disk image, create a new image.
      mkdir -p "${VMDIR}" 2>/dev/null
      case ${preallocation} in
        off|metadata|falloc|full) true;;
        *)
          echo "ERROR! ${preallocation} is an unsupported disk preallocation option."
          exit 1;;
      esac

      # https://blog.programster.org/qcow2-performance
      if ! ${QEMU_IMG} create -q -f qcow2 -o lazy_refcounts=on,preallocation="${preallocation}" "${disk_img}" "${disk_size}"; then
        echo "ERROR! Failed to create ${disk_img}"
        exit 1
      fi

      if [ -z "${iso}" ] && [ -z "${img}" ]; then
        echo "ERROR! You haven't specified a .iso or .img image to boot from."
        exit 1
      fi
      echo "             Just created, booting from ${iso}${img}"
      DISK_USED="no"
  elif [ -e "${disk_img}" ]; then
    # Check there isn't already a process attached to the disk image.
    if ! ${QEMU_IMG} info "${disk_img}" >/dev/null; then
      echo "             Failed to get \"write\" lock. Is another process using the disk?"
      exit 1
    else
      # Only check disk image size if preallocation is off
      if [ "${preallocation}" == "off" ]; then
        DISK_CURR_SIZE=$(stat -c%s "${disk_img}")
        if [ "${DISK_CURR_SIZE}" -le "${DISK_MIN_SIZE}" ]; then
          echo "             Looks unused, booting from ${iso}${img}"
          if [ -z "${iso}" ] && [ -z "${img}" ]; then
            echo "ERROR! You haven't specified a .iso or .img image to boot from."
            exit 1
          fi
        else
          DISK_USED="yes"
        fi
      else
        DISK_USED="yes"
      fi
    fi
  fi

  if [ "${DISK_USED}" == "yes" ] && [ "${guest_os}" != "kolibrios" ]; then
    # If there is a disk image that appears to be used do not boot from installation media.
    iso=""
    img=""
  fi

  if [ "${guest_os}" == "macos" ] || [ "${guest_os}" == "windows" ]; then
    # Display MSRs alert if the guest is macOS or windows
    ignore_msrs_alert
  fi

  # Has the status quo been requested?
  if [ "${STATUS_QUO}" == "-snapshot" ]; then
    if [ -z "${img}" ] && [ -z "${iso}" ]; then
      echo "             Existing disk state will be preserved, no writes will be committed."
    fi
  fi

  if [ -n "${iso}" ] && [ -e "${iso}" ]; then
    echo " - Boot ISO: ${iso}"
  elif [ -n "${img}" ] && [ -e "${img}" ]; then
    echo " - Recovery: ${img}"
  fi

  if [ -n "${fixed_iso}" ] && [ -e "${fixed_iso}" ]; then
    echo " - CD-ROM:   ${fixed_iso}"
  fi

  # Setup the appropriate audio device based on the display output
  case ${OUTPUT} in
    spice|spice-app|none) AUDIO_DEV="spice,id=audio0";;
    *) AUDIO_DEV="pa,id=audio0,out.mixing-engine=off,out.stream-name=${LAUNCHER}-${VMNAME},in.stream-name=${LAUNCHER}-${VMNAME}";;
  esac

  # Determine a sane resolution for Linux guests.
  if [ "${guest_os}" == "linux" ]; then
    local X_RES=1152
    local Y_RES=648
    if [ "${XDG_SESSION_TYPE}" == "x11" ]; then
      local LOWEST_WIDTH=""
      if [ -z "${SCREEN}" ]; then
        LOWEST_WIDTH=$(xrandr --listmonitors | grep -v Monitors | cut -d' ' -f4 | cut -d'/' -f1 | sort | head -n1)
      else
        LOWEST_WIDTH=$(xrandr --listmonitors | grep -v Monitors | grep "^ ${SCREEN}:" | cut -d' ' -f4 | cut -d'/' -f1 | head -n1)
      fi
      if [ "${FULLSCREEN}" ]; then
        if [ -z "${SCREEN}" ]; then
          X_RES=$(xrandr --listmonitors | grep -v Monitors | cut -d' ' -f4 | cut -d'/' -f1 | sort | head -n1)
          Y_RES=$(xrandr --listmonitors | grep -v Monitors | cut -d' ' -f4 | cut -d'/' -f2 | cut -d'x' -f2 | sort | head -n1)
        else
          X_RES=$(xrandr --listmonitors | grep -v Monitors | grep "^ ${SCREEN}:" | cut -d' ' -f4 | cut -d'/' -f1 | head -n1)
          Y_RES=$(xrandr --listmonitors | grep -v Monitors | grep "^ ${SCREEN}:" | cut -d' ' -f4 | cut -d'/' -f2 | cut -d'x' -f2 | head -n1)
        fi
      elif [ "${LOWEST_WIDTH}" -ge 3840 ]; then
        X_RES=3200
        Y_RES=1800
      elif [ "${LOWEST_WIDTH}" -ge 2560 ]; then
        X_RES=2048
        Y_RES=1152
      elif [ "${LOWEST_WIDTH}" -ge 1920 ]; then
        X_RES=1664
        Y_RES=936
      elif [ "${LOWEST_WIDTH}" -ge 1280 ]; then
        X_RES=1152
        Y_RES=648
      fi
    fi
  fi

  # https://www.kraxel.org/blog/2019/09/display-devices-in-qemu/
  if [ "${guest_os}" == "linux" ]; then
    case ${OUTPUT} in
      none|spice) DISPLAY_DEVICE="qxl-vga";;
      *) DISPLAY_DEVICE="virtio-vga";;
    esac
  elif [ "${guest_os}" == "macos" ]; then
    # Displays in System Preferences can be used to select a resolution if:
    #  - Mojave only offers 4:3 resolutions
    #  - High Sierra will run at the default 1920x1080 only.
    # QXL prevents seamless mouse working with a SPICE client
    #  - https://github.com/wimpysworld/quickemu/issues/222
    DISPLAY_DEVICE="VGA"
  elif [ "${guest_os}" == "windows" ]; then
    DISPLAY_DEVICE="qxl-vga"
  else
    DISPLAY_DEVICE="qxl-vga"
  fi

  echo -n " - Display:  ${OUTPUT^^}, ${DISPLAY_DEVICE}"

  # Map Quickemu OUTPUT to QEMU -display
  case ${OUTPUT} in
    gtk)
      DISPLAY_RENDER="${OUTPUT},grab-on-hover=on,zoom-to-fit=off"
      # GL is not working with GTK and virtio-vga
      if [ "${DISPLAY_DEVICE}" == "virtio-vga" ]; then
        gl="off"
      fi
      ;;
    none|spice)
      DISPLAY_RENDER="none";;
    # spice-app has a bug where using spice-app,gl=off seems to enable gl, but
    # if the gl param is omitted it works fine.
    spice-app)
      DISPLAY_RENDER="${OUTPUT}"
      if [[ "${gl}" == "on" ]]; then
        DISPLAY_RENDER+=",gl=on"
      fi
      ;;
    *)
      DISPLAY_RENDER="${OUTPUT},gl=${gl}";;
  esac

  if [ "${gl}" == "on" ] && [[ "${DISPLAY_DEVICE}" == *"virtio"* ]]; then
    if [ "${QEMU_VER_SHORT}" -ge 61 ]; then
      DISPLAY_DEVICE="${DISPLAY_DEVICE}-gl"
    else
      DISPLAY_DEVICE="${DISPLAY_DEVICE},virgl=on"
    fi
    echo ", GL (${gl}), VirGL (on)"
  else
    echo ", GL (${gl}), VirGL (off)"
  fi

  # Build the video configuration
  VIDEO="-device ${DISPLAY_DEVICE}"

  # Try and coerce the display resolution for Linux guests only.
  if [ "${guest_os}" == "linux" ]; then
    VIDEO="${VIDEO},xres=${X_RES},yres=${Y_RES}"
  fi

  # Allocate VRAM to VGA devices
  case ${DISPLAY_DEVICE} in
    bochs-display) VIDEO="${VIDEO},vgamem=67108864";;
    qxl|qxl-vga) VIDEO="${VIDEO},ram_size=65536,vram_size=65536,vgamem_mb=64";;
    ati-vga|cirrus-vga|VGA) VIDEO="${VIDEO},vgamem_mb=64";;
  esac

  # Configure multiscreen if max_outputs was provided in the .conf file
  if [ -v max_outputs ]; then
    VIDEO="${VIDEO},max_outputs=${max_outputs}"
  fi

  # Add fullscreen options
  VIDEO="${VIDEO} ${FULLSCREEN}"

  # Set the hostname of the VM
  local NET="user,hostname=${VMNAME}"

  echo -n "" > "${VMDIR}/${VMNAME}.ports"

  if [ -z "$SSH_PORT" ]; then
    # Find a free port to expose ssh to the guest
    SSH_PORT=$(get_port 22220 9)
  fi

  if [ -n "${SSH_PORT}" ]; then
    echo "ssh,${SSH_PORT}" >> "${VMDIR}/${VMNAME}.ports"
    NET="${NET},hostfwd=tcp::${SSH_PORT}-:22"
    echo " - ssh:      On host:  ssh user@localhost -p ${SSH_PORT}"
  else
    echo " - ssh:      All ssh ports have been exhausted."
  fi

  # Have any port forwards been requested?
  if (( ${#port_forwards[@]} )); then
    echo " - PORTS:    Port forwards requested:"
    for FORWARD in "${port_forwards[@]}"; do
      HOST_PORT=$(echo "${FORWARD}" | cut -d':' -f1)
      GUEST_PORT=$(echo "${FORWARD}" | cut -d':' -f2)
      echo "              - ${HOST_PORT} => ${GUEST_PORT}"
      NET="${NET},hostfwd=tcp::${HOST_PORT}-:${GUEST_PORT}"
    done
  fi

  local SPICE="disable-ticketing=on"
  if [ -z "${SPICE_PORT}" ]; then
    # Find a free port for spice
    SPICE_PORT=$(get_port 5930 9)
  fi

  if [ -z "${SPICE_PORT}" ]; then
    echo " - SPICE:    All SPICE ports have been exhausted."
    if [ "${OUTPUT}" == "none" ] || [ "${OUTPUT}" == "spice" ] || [ "${OUTPUT}" == "spice-app" ]; then
      echo "             ERROR! Requested SPICE display, but no SPICE ports are free."
      exit 1
    fi
  else
    if [ "${OUTPUT}" == "spice-app" ]; then
      echo " - SPICE:    Enabled"
    else
      echo "spice,${SPICE_PORT}" >> "${VMDIR}/${VMNAME}.ports"
      echo -n " - SPICE:    On host:  spicy --title \"${VMNAME}\" --port ${SPICE_PORT}"
      if [ "${guest_os}" != "macos" ] && [ -n "${PUBLIC}" ]; then
        echo -n " --spice-shared-dir ${PUBLIC}"
      fi
      echo "${FULLSPICY}"
      SPICE="${SPICE},port=${SPICE_PORT}"
    fi

    if [ -n "${PUBLIC}" ]; then
      case ${guest_os} in
        macos)
          # Reference: https://gitlab.gnome.org/GNOME/phodav/-/issues/5
          echo " - WebDAV:   On guest: build spice-webdavd (https://gitlab.gnome.org/GNOME/phodav/-/merge_requests/24)"
          echo " - WebDAV:   On guest: Finder -> Connect to Server -> http://localhost:9843/"
          ;;
        *)
          echo " - WebDAV:   On guest: dav://localhost:9843/";;
      esac
    fi
  fi

  if [ "${guest_os}" != "windows" ] && [ -n "${PUBLIC}" ]; then
    echo -n " - 9P:       On guest: "
    if [ "${guest_os}" == "linux" ]; then
      echo "sudo mount -t 9p -o trans=virtio,version=9p2000.L,msize=104857600 ${PUBLIC_TAG} ~/$(basename "${PUBLIC}")"
    elif [ "${guest_os}" == "macos" ]; then
      # PUBLICSHARE needs to be world writeable for seamless integration with
      # macOS. Test if it is world writeable, and prompt what to do if not.
      echo "sudo mount_9p ${PUBLIC_TAG}"
      if [ "${PUBLIC_PERMS}" != "drwxrwxrwx" ]; then
        echo " - 9P:       On host:  chmod 777 ${PUBLIC}"
        echo "             Required for macOS integration 👆"
      fi
    fi
  fi

  # If smbd is available and ~/Public is present export it to the guest via samba
  if [[ -e "/usr/sbin/smbd" && -n ${PUBLIC} ]]; then
      NET="${NET},smb=${PUBLIC}"
      echo " - smbd:     On guest: smb://10.0.2.4/qemu"
  fi

  enable_usb_passthrough

  echo "#!/usr/bin/env bash" > "${VMDIR}/${VMNAME}.sh"

  # Start TPM
  if [ "${tpm}" == "on" ]; then
    local tpm_args=()
    # shellcheck disable=SC2054
    tpm_args+=(socket
            --ctrl type=unixio,path="${VMDIR}/${VMNAME}.swtpm-sock"
            --terminate
            --tpmstate dir="${VMDIR}"
            --tpm2)
    echo "${SWTPM} ${tpm_args[*]} &" >> "${VMDIR}/${VMNAME}.sh"
    ${SWTPM} "${tpm_args[@]}" >> "${VMDIR}/${VMNAME}.log" &
    echo " - TPM:      ${VMDIR}/${VMNAME}.swtpm-sock (${!})"
    sleep 0.25
  fi

  # Boot the VM
  local args=()

  # shellcheck disable=SC2054,SC2206,SC2140
  args+=(-name ${VMNAME},process=${VMNAME} -pidfile "${VMDIR}/${VMNAME}.pid"
         -enable-kvm -machine ${MACHINE_TYPE},smm=${SMM},vmport=off ${GUEST_TWEAKS}
         ${CPU} ${SMP}
         -m ${RAM_VM} ${BALLOON}
         -smbios type=2,manufacturer="Quickemu Project",product="Quickemu",version="${VERSION}",serial="0xDEADBEEF",location="quickemu.com",asset="${VMNAME}"
         ${VIDEO} -display ${DISPLAY_RENDER}
         -audiodev ${AUDIO_DEV}
         -device intel-hda -device hda-duplex,audiodev=audio0
         -rtc base=localtime,clock=host,driftfix=slew
         -spice ${SPICE}
         -device virtio-serial-pci
         -chardev socket,id=agent0,path="${VMDIR}/${VMNAME}-agent.sock",server=on,wait=off
         -device virtserialport,chardev=agent0,name=org.qemu.guest_agent.0
         -chardev spicevmc,id=vdagent0,name=vdagent
         -device virtserialport,chardev=vdagent0,name=com.redhat.spice.0
         -chardev spiceport,id=webdav0,name=org.spice-space.webdav.0
         -device virtserialport,chardev=webdav0,name=org.spice-space.webdav.0
         -device virtio-rng-pci,rng=rng0
         -object rng-random,id=rng0,filename=/dev/urandom
         -device ${USB_HOST_PASSTHROUGH_CONTROLLER},id=spicepass
         -chardev spicevmc,id=usbredirchardev1,name=usbredir
         -device usb-redir,chardev=usbredirchardev1,id=usbredirdev1
         -chardev spicevmc,id=usbredirchardev2,name=usbredir
         -device usb-redir,chardev=usbredirchardev2,id=usbredirdev2
         -chardev spicevmc,id=usbredirchardev3,name=usbredir
         -device usb-redir,chardev=usbredirchardev3,id=usbredirdev3
         -device pci-ohci,id=smartpass
         -device usb-ccid
         -chardev spicevmc,id=ccid,name=smartcard
         -device ccid-card-passthru,chardev=ccid
         )


  # setup usb-controller
  [ -z "${USB_CONTROLLER}" ] && USB_CONTROLLER="$usb_controller"
  if [ "${USB_CONTROLLER}" == "ehci" ]; then
    args+=(-device usb-ehci,id=input)
  elif [ "${USB_CONTROLLER}" == "xhci" ]; then
    args+=(-device qemu-xhci,id=input)
  elif [ -z "${USB_CONTROLLER}" ] || [ "${USB_CONTROLLER}" == "none" ]; then
    # add nothing
    :
  else
    echo "WARNING! Unknown usb-controller value: '${USB_CONTROLLER}'"
  fi

  # setup keyboard
  # @INFO: must be set after usb-controller
  [ -z "${KEYBOARD}" ] && KEYBOARD="$keyboard"
  if [ "${KEYBOARD}" == "usb" ]; then
    args+=(-device usb-kbd,bus=input.0)
  elif [ "${KEYBOARD}" == "virtio" ]; then
    args+=(-device virtio-keyboard)
  elif [ "${KEYBOARD}" == "ps2" ] || [ -z "${KEYBOARD}" ]; then
    # add nothing, default is ps/2 keyboard
    :
  else
    echo "WARNING! Unknown keyboard value: '${KEYBOARD}'; Fallback to ps2"
  fi

  # setup keyboard_layout
  # @INFO: When using the VNC display, you must use the -k parameter to set the keyboard layout if you are not using en-us.
  [ -z "${KEYBOARD_LAYOUT}" ] && KEYBOARD_LAYOUT="$keyboard_layout"
  if [ -n "${KEYBOARD_LAYOUT}" ]; then
    args+=(-k ${KEYBOARD_LAYOUT})
  fi

  # FIXME: Check for device availability. qemu will fail to start otherwise
  if [ -n "${BRAILLE}" ]; then
    # shellcheck disable=SC2054
    args+=(-chardev braille,id=brltty
           -device usb-braille,id=usbbrl,chardev=brltty)
  fi

  # setup mouse
  # @INFO: must be set after usb-controller
  [ -z "${MOUSE}" ] && MOUSE="$mouse"
  if [ "${MOUSE}" == "usb" ]; then
    args+=(-device usb-mouse,bus=input.0)
  elif [ "${MOUSE}" == "tablet" ]; then
    args+=(-device usb-tablet,bus=input.0)
  elif [ "${MOUSE}" == "virtio" ]; then
    args+=(-device virtio-mouse)
  elif [ "${MOUSE}" == "ps2" ] || [ -z "${MOUSE}" ]; then
    # add nothing, default is ps/2 mouse
    :
  else
    echo "WARNING! Unknown mouse value: '${MOUSE}; Fallback to ps2'"
  fi

  if [ -n "${bridge}" ]; then
    # Enable bridge mode networking
    # shellcheck disable=SC2054,SC2206
    args+=(-nic bridge,br=${bridge},model=virtio-net-pci)
  else
    # shellcheck disable=SC2054,SC2206
    args+=(-device ${NET_DEVICE},netdev=nic -netdev ${NET},id=nic)
  fi

  # Add the disks
  # - https://turlucode.com/qemu-disk-io-performance-comparison-native-or-threads-windows-10-version/
  if [[ "${boot}" == *"efi"* ]]; then
    # shellcheck disable=SC2054
    args+=(-global driver=cfi.pflash01,property=secure,value=on
           -drive if=pflash,format=raw,unit=0,file="${EFI_CODE}",readonly=on
           -drive if=pflash,format=raw,unit=1,file="${EFI_VARS}")
  fi

  if [ -n "${floppy}" ]; then
    # shellcheck disable=SC2054
    args+=(-drive if=floppy,format=raw,file="${floppy}")
  fi

  if [ -n "${iso}" ]; then
    # shellcheck disable=SC2054
    args+=(-drive media=cdrom,index=0,file="${iso}")
  fi

  if [ -n "${fixed_iso}" ]; then
    # shellcheck disable=SC2054
    args+=(-drive media=cdrom,index=1,file="${fixed_iso}")
  fi

  if [ -n "${iso}" ] && [ "${guest_os}" == "freedos" ]; then
    # FreeDOS reboots after partitioning the disk, and QEMU tries to boot from disk after first restart
    # This flag sets the boot order to cdrom,disk. It will persist until powering down the VM
    args+=(-boot order=dc)
  elif [ -n "${iso}" ] && [ "${guest_os}" == "kolibrios" ]; then
    # Since there is bug (probably) in KolibriOS: cdrom indexes 0 or 1 make system show an extra unexisting iso, so we use index=2
    # shellcheck disable=SC2054
    args+=(-drive media=cdrom,index=2,file="${iso}")
    iso=""
  elif [ -n "${iso}" ] && [ "${guest_os}" == "windows" ] && [ -e "${VMDIR}/unattended.iso" ]; then
    # Attach the unattended configuration to Windows guests when booting from ISO
    # shellcheck disable=SC2054
    args+=(-drive media=cdrom,index=2,file="${VMDIR}/unattended.iso")
  fi

  if [ "${guest_os}" == "macos" ]; then
    # shellcheck disable=SC2054
    args+=(-device ahci,id=ahci
           -device ide-hd,bus=ahci.0,drive=BootLoader,bootindex=0
           -drive id=BootLoader,if=none,format=qcow2,file="${MAC_BOOTLOADER}")

    if [ -n "${img}" ]; then
      # shellcheck disable=SC2054
      args+=(-device ide-hd,bus=ahci.1,drive=RecoveryImage
             -drive id=RecoveryImage,if=none,format=raw,file="${img}")
    fi

    # shellcheck disable=SC2054,SC2206
    args+=(-device ${MAC_DISK_DEV},drive=SystemDisk
           -drive id=SystemDisk,if=none,format=qcow2,file="${disk_img}" ${STATUS_QUO})
  elif [ "${guest_os}" == "kolibrios" ]; then
    # shellcheck disable=SC2054,SC2206
    args+=(-device ahci,id=ahci
           -device ide-hd,bus=ahci.0,drive=SystemDisk
           -drive id=SystemDisk,if=none,format=qcow2,file="${disk_img}" ${STATUS_QUO})
  elif [ "${guest_os}" == "batocera" ] ; then
    # shellcheck disable=SC2054,SC2206
    args+=(-device virtio-blk-pci,drive=BootDisk
           -drive id=BootDisk,if=none,format=raw,file="${img}"
           -device virtio-blk-pci,drive=SystemDisk
           -drive id=SystemDisk,if=none,format=qcow2,file="${disk_img}" ${STATUS_QUO})
  else
    # shellcheck disable=SC2054,SC2206
    args+=(-device virtio-blk-pci,drive=SystemDisk
           -drive id=SystemDisk,if=none,format=qcow2,file="${disk_img}" ${STATUS_QUO})
  fi

  # https://wiki.qemu.org/Documentation/9psetup
  # https://askubuntu.com/questions/772784/9p-libvirt-qemu-share-modes
  if [ "${guest_os}" != "windows" ] && [ -n "${PUBLIC}" ]; then
      # shellcheck disable=SC2054
      args+=(-fsdev local,id=fsdev0,path="${PUBLIC}",security_model=mapped-xattr
             -device virtio-9p-pci,fsdev=fsdev0,mount_tag="${PUBLIC_TAG}")
  fi

  if [ -n "${USB_PASSTHROUGH}" ]; then
    # shellcheck disable=SC2054,SC2206
    args+=(-device ${USB_HOST_PASSTHROUGH_CONTROLLER},id=hostpass
          ${USB_PASSTHROUGH})
  fi

  if [ "${tpm}" == "on" ] && [ -S "${VMDIR}/${VMNAME}.swtpm-sock" ]; then
      # shellcheck disable=SC2054
      args+=(-chardev socket,id=chrtpm,path="${VMDIR}/${VMNAME}.swtpm-sock"
            -tpmdev emulator,id=tpm0,chardev=chrtpm
            -device tpm-tis,tpmdev=tpm0)
  fi

  if [ -z "${MONITOR}" ]; then
    MONITOR="${monitor:-none}"
  fi

  if [ -z "${MONITOR_TELNET_HOST}" ]; then
    MONITOR_TELNET_HOST="${monitor_telnet_host:-localhost}"
  fi
  if [ -z "${MONITOR_TELNET_PORT}" ]; then
    MONITOR_TELNET_PORT="${monitor_telnet_port}"
  fi
  if [ -n "${MONITOR_TELNET_PORT}" ] &&  ! is_numeric "${MONITOR_TELNET_PORT}"; then
    echo "ERROR: telnet-port must be a number!"
    exit 1
  fi

  if [ "${MONITOR}" == "none" ]; then
    args+=(-monitor none)
    echo " - Monitor:  (off)"
  elif [ "${MONITOR}" == "telnet" ]; then
    # Find a free port to expose monitor-telnet to the guest
    local temp_port="$(get_port ${MONITOR_TELNET_PORT} 9)"
    if [ -z "${temp_port}" ]; then
      echo " - Monitor:  All Monitor-Telnet ports have been exhausted."
    else
      MONITOR_TELNET_PORT="${temp_port}"
      args+=(-monitor telnet:${MONITOR_TELNET_HOST}:${MONITOR_TELNET_PORT},server,nowait)
      echo " - Monitor:  On host:  telnet ${MONITOR_TELNET_HOST} ${MONITOR_TELNET_PORT}"
      echo "monitor-telnet,${MONITOR_TELNET_PORT},${MONITOR_TELNET_HOST}" >> "${VMDIR}/${VMNAME}.ports"
    fi
  elif [ "${MONITOR}" == "socket" ]; then
    args+=(-monitor unix:${VM_MONITOR_SOCKETPATH},server,nowait)
    echo " - Monitor:  On host:  nc -U \"${VM_MONITOR_SOCKETPATH}\""
    echo "             or     :  socat -,echo=0,icanon=0 unix-connect:${VM_MONITOR_SOCKETPATH}"
  else
    echo "ERROR! \"${MONITOR}\" is an unknown monitor option."
    exit 1
  fi

  if [ -z "${SERIAL}" ]; then
    SERIAL="${serial:-none}"
  fi

  if [ -z "${SERIAL_TELNET_HOST}" ]; then
    SERIAL_TELNET_HOST="${serial_telnet_host:-localhost}"
  fi
  if [ -z "${SERIAL_TELNET_PORT}" ]; then
    SERIAL_TELNET_PORT="${serial_telnet_port}"
  fi
  if [ -n "${SERIAL_TELNET_PORT}" ] &&  ! is_numeric "${SERIAL_TELNET_PORT}"; then
    echo "ERROR: serial-port must be a number!"
    exit 1
  fi

  if [ "${SERIAL}" == "none" ]; then
    args+=(-serial none)
  elif [ "${SERIAL}" == "telnet" ]; then
    # Find a free port to expose serial-telnet to the guest
    local temp_port="$(get_port ${SERIAL_TELNET_PORT} 9)"
    if [ -z "${temp_port}" ]; then
      echo " - Serial:   All Serial-Telnet ports have been exhausted."
    else
      SERIAL_TELNET_PORT="${temp_port}"
      args+=(-serial telnet:${SERIAL_TELNET_HOST}:${SERIAL_TELNET_PORT},server,nowait)
      echo " - Serial:   On host:  telnet ${SERIAL_TELNET_HOST} ${SERIAL_TELNET_PORT}"
      echo "serial-telnet,${SERIAL_TELNET_PORT},${SERIAL_TELNET_HOST}" >> "${VMDIR}/${VMNAME}.ports"
    fi
  elif [ "${SERIAL}" == "socket" ]; then
    args+=(-serial unix:${VM_SERIAL_SOCKETPATH},server,nowait)
    echo " - Serial:   On host:  nc -U \"${VM_SERIAL_SOCKETPATH}\""
    echo "             or     :  socat -,echo=0,icanon=0 unix-connect:${VM_SERIAL_SOCKETPATH}"
  else
    echo "ERROR! \"${SERIAL}\" is an unknown serial option."
    exit 1
  fi


  if [ -z "${EXTRA_ARGS}" ]; then
    EXTRA_ARGS="${extra_args}"
  fi
  if [ -n "${EXTRA_ARGS}" ]; then
      args+=(${EXTRA_ARGS})
  fi

  # The OSK parameter contains parenthesis, they need to be escaped in the shell
  # scripts. The vendor name, Quickemu Project, contains a space. It needs to be
  # double-quoted.
  SHELL_ARGS="${args[*]}"
  SHELL_ARGS="${SHELL_ARGS//\(/\\(}"
  SHELL_ARGS="${SHELL_ARGS//)/\\)}"
  SHELL_ARGS="${SHELL_ARGS//Quickemu Project/\"Quickemu Project\"}"

  if [ ${VM_UP} -eq 0 ]; then
    echo "${QEMU}" "${SHELL_ARGS}" >> "${VMDIR}/${VMNAME}.sh"
    ${QEMU} "${args[@]}" > "${VMDIR}/${VMNAME}.log" &
    sleep 0.25
  fi

  echo " - Process:  Starting ${VM} as ${VMNAME} ($(cat "${VMDIR}/${VMNAME}.pid"))"
}

function start_viewer {
  errno=0
  if [ "${VIEWER}" != "none" ]; then
    echo "---"

    # If output is 'none' then SPICE was requested.
    if [ "${OUTPUT}" == "spice" ]; then
      if [ "${VIEWER}" == "remote-viewer" ]; then
        # show via viewer: remote-viewer

        if [ -n "${PUBLIC}" ]; then
          echo " - Start viewer: ${VIEWER} --title \"${VMNAME}\" --spice-shared-dir \"${PUBLIC}\" ${FULLSPICY} \"spice://localhost:${SPICE_PORT}\" >/dev/null 2>&1 &"
          ${VIEWER} --title "${VMNAME}" --spice-shared-dir "${PUBLIC}" ${FULLSPICY} "spice://localhost:${SPICE_PORT}" >/dev/null 2>&1 &
          errno=$?
        else
          echo " - Start viewer: ${VIEWER} --title \"${VMNAME}\" ${FULLSPICY} \"spice://localhost:${SPICE_PORT}\" >/dev/null 2>&1 &"
          ${VIEWER} --title "${VMNAME}" ${FULLSPICY} "spice://localhost:${SPICE_PORT}" >/dev/null 2>&1 &
          errno=$?
        fi

      elif [ "${VIEWER}" == "spicy" ]; then
        # show via viewer: spicy

        if [ -n "${PUBLIC}" ]; then
          echo " - Start viewer: ${VIEWER} --title \"${VMNAME}\" --port \"${SPICE_PORT}\" --spice-shared-dir \"${PUBLIC}\" \"${FULLSPICY}\" >/dev/null 2>&1 &"
          ${VIEWER} --title "${VMNAME}" --port "${SPICE_PORT}" --spice-shared-dir "${PUBLIC}" "${FULLSPICY}" >/dev/null 2>&1 &
          errno=$?
        else
          echo " - Start viewer: ${VIEWER} --title \"${VMNAME}\" --port \"${SPICE_PORT}\" \"${FULLSPICY}\" >/dev/null 2>&1 &"
          ${VIEWER} --title "${VMNAME}" --port "${SPICE_PORT}" "${FULLSPICY}" >/dev/null 2>&1 &
          errno=$?
        fi
      fi
      if [ $errno -ne 0 ]; then
        echo "WARNING! Could not start viewer(${VIEWER}) Err: $errno"
      fi
    fi
  fi
}

function shortcut_create {
  local dirname="${HOME}/.local/share/applications"
  local filename="${HOME}/.local/share/applications/${VMNAME}.desktop"

  if [ ! -d "${dirname}" ]; then
    mkdir -p "${dirname}"
  fi
  cat << EOF > "${filename}"
[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Exec=${0} --vm ${VM}
Path=${VMPATH}
Name=${VMNAME}
Icon=/usr/share/icons/hicolor/scalable/apps/qemu.svg
EOF
  echo "Created ${VMNAME}.desktop file"
}

function usage() {
  echo
  echo "Usage"
  echo "  ${LAUNCHER} --vm ubuntu.conf"
  echo
  echo "You can also pass optional parameters"
  echo "  --braille                         : Enable braille support. Requires SDL."
  echo "  --delete-disk                     : Delete the disk image and EFI variables"
  echo "  --delete-vm                       : Delete the entire VM and it's configuration"
  echo "  --display                         : Select display backend. 'sdl' (default), 'gtk', 'none', 'spice' or 'spice-app'"
  echo "  --fullscreen                      : Starts VM in full screen mode (Ctl+Alt+f to exit)"
  echo "  --ignore-msrs-always              : Configure KVM to always ignore unhandled machine-specific registers"
  echo "  --screen <screen>                 : Use specified screen to determine the window size."
  echo "  --shortcut                        : Create a desktop shortcut"
  echo "  --snapshot apply <tag>            : Apply/restore a snapshot."
  echo "  --snapshot create <tag>           : Create a snapshot."
  echo "  --snapshot delete <tag>           : Delete a snapshot."
  echo "  --snapshot info                   : Show disk/snapshot info."
  echo "  --status-quo                      : Do not commit any changes to disk/snapshot."
  echo "  --viewer <viewer>                 : Choose an alternative viewer. @Options: 'spicy' (default), 'remote-viewer', 'none'"
  echo "  --ssh-port <port>                 : Set ssh-port manually"
  echo "  --spice-port <port>               : Set spice-port manually"
  echo "  --public-dir <path>               : expose share directory. @Options: '' (default: xdg-user-dir PUBLICSHARE), '<directory>', 'none'"
  echo "  --monitor <type>                  : Set monitor connection type. @Options: 'socket' (default), 'telnet', 'none'"
  echo "  --monitor-telnet-host <ip/host>   : Set telnet host for monitor. (default: 'localhost')"
  echo "  --monitor-telnet-port <port>      : Set telnet port for monitor. (default: '4440')"
  echo "  --monitor-cmd <cmd>               : Send command to monitor if available. (Example: system_powerdown)"
  echo "  --serial <type>                   : Set serial connection type. @Options: 'socket' (default), 'telnet', 'none'"
  echo "  --serial-telnet-host <ip/host>    : Set telnet host for serial. (default: 'localhost')"
  echo "  --serial-telnet-port <port>       : Set telnet port for serial. (default: '6660')"
  echo "  --keyboard <type>                 : Set keyboard. @Options: 'usb' (default), 'ps2', 'virtio'"
  echo "  --keyboard_layout <layout>        : Set keyboard layout."
  echo "  --mouse <type>                    : Set mouse. @Options: 'tablet' (default), 'ps2', 'usb', 'virtio'"
  echo "  --usb-controller <type>           : Set usb-controller. @Options: 'ehci' (default), 'xhci', 'none'"
  echo "  --extra_args <arguments>          : Pass additional arguments to qemu"
  echo "  --version                         : Print version"
  exit 1
}

function display_param_check() {
  if [ "${OUTPUT}" != "gtk" ] && [ "${OUTPUT}" != "none" ] && [ "${OUTPUT}" != "sdl" ] && [ "${OUTPUT}" != "spice" ] && [ "${OUTPUT}" != "spice-app" ]; then
    echo "ERROR! Requested output '${OUTPUT}' is not recognised."
    exit 1
  fi
}

function viewer_param_check() {
  if [ "${VIEWER}" != "none" ] && [ "${VIEWER}" != "spicy" ] && [ "${VIEWER}" != "remote-viewer" ]; then
    echo "ERROR! Requested viewer '${VIEWER}' is not recognised."
    exit 1
  fi
  if [ "${VIEWER}" == "spicy" ] && ! command -v spicy &>/dev/null; then
    echo "ERROR! Requested 'spicy' as viewer, but 'spicy' is not installed."
    exit 1
  elif [ "${VIEWER}" == "remote-viewer" ] && ! command -v remote-viewer &>/dev/null; then
    echo "ERROR! Requested 'remote-viewer' as viewer, but 'remote-viewer' is not installed."
    exit 1
  fi
}

function parse_ports_from_file {
    local FILE="${VMDIR}/${VMNAME}.ports"

    # parse ports
    local port_name=( $(cat "$FILE" | cut -d, -f1) )
    local port_number=( $(cat "$FILE" | cut -d, -f2) )
    local host_name=( $(cat "$FILE" | gawk 'FS="," {print $3,"."}') )

    for ((i=0; i<${#port_name[@]}; i++)); do
      if [ "${port_name[$i]}" == "ssh" ]; then
        SSH_PORT="${port_number[$i]}"
      elif [ "${port_name[$i]}" == "spice" ]; then
        SPICE_PORT="${port_number[$i]}"
      elif [ "${port_name[$i]}" == "monitor-telnet" ]; then
        MONITOR_TELNET_PORT="${port_number[$i]}"
        MONITOR_TELNET_HOST="${host_name[$i]}"
      elif [ "${port_name[$i]}" == "serial-telnet" ]; then
        SERIAL_TELNET_PORT="${port_number[$i]}"
        SERIAL_TELNET_HOST="${host_name[$i]}"
      fi
    done
}

function is_numeric {
    [[ "$1" =~ ^[0-9]+$ ]]
}

function monitor_send_cmd {
    local MSG="${1}"

    if [ -z "${MSG}" ]; then
        echo "WARNING! Send to QEMU-Monitor: Message empty!"
        return 1
    fi

    # Determine monitor channel
    local monitor_channel=""

    if [ -S "${VMDIR}/${VMNAME}-monitor.socket" ]; then
        monitor_channel="socket"
    elif [ -n "${MONITOR_TELNET_PORT}" ] && [ -n "${MONITOR_TELNET_HOST}" ]; then
        monitor_channel="telnet"
    else
        echo "WARNING! No qemu-monitor channel available - Couldn't send message to monitor!"
        return
    fi


    case "${monitor_channel}" in
        socket)
            echo -e " - MON-SEND: ${MSG}"
            echo -e "${MSG}" | socat -,shut-down unix-connect:"${VM_MONITOR_SOCKETPATH}" 2>&1 > /dev/null
            ;;
        telnet)
            echo -e " - MON-SEND: ${MSG}"
            echo -e "${MSG}" | socat - tcp:"${MONITOR_TELNET_HOST}":"${MONITOR_TELNET_PORT}" 2>&1 > /dev/null
            ;;
        *)
            echo "ERROR! This should never happen!"
            exit 1
            ;;
    esac

    return 0
}

### MAIN

# Lowercase variables are used in the VM config file only
boot="efi"
bridge=""
cpu_cores=""
disk_img=""
disk_size=""
extra_args=""
fixed_iso=""
floppy=""
guest_os="linux"
img=""
iso=""
macos_release=""
port_forwards=()
preallocation="off"
ram=""
secureboot="off"
tpm="off"
usb_devices=()
viewer="spicy"
ssh_port=""
spice_port=""
public_dir=""
monitor="socket"
monitor_telnet_port="4440"
monitor_telnet_host="localhost"
monitor_cmd=""
serial="socket"
serial_telnet_port="6660"
serial_telnet_host="localhost"
# options: ehci(USB2.0), xhci(USB3.0)
usb_controller="ehci"
# options: ps2, usb, virtio
keyboard="usb"
keyboard_layout="en-us"
# options: ps2, usb, tablet, virtio
mouse="tablet"
# options: ehci, xhci
usb_controller="ehci"

BRAILLE=""
DELETE_DISK=0
DELETE_VM=0
FULLSCREEN=""
FULLSPICY=""
OUTPUT=""
PUBLIC=""
PUBLIC_PERMS=""
PUBLIC_TAG=""
SCREEN=""
SHORTCUT=0
SNAPSHOT_ACTION=""
SNAPSHOT_TAG=""
STATUS_QUO=""
USB_PASSTHROUGH=""
VM=""
VMDIR=""
VMNAME=""
VMPATH=""
VIEWER=""
SSH_PORT=""
SPICE_PORT=""
MONITOR=""
MONITOR_TELNET_PORT=""
MONITOR_TELNET_HOST=""
MONITOR_CMD=""
VM_MONITOR_SOCKETPATH=""
VM_SERIAL_SOCKETPATH=""
SERIAL=""
SERIAL_TELNET_PORT=""
SERIAL_TELNET_HOST=""
KEYBOARD=""
KEYBOARD_LAYOUT=""
MOUSE=""
USB_CONTROLLER=""
EXTRA_ARGS=""

# shellcheck disable=SC2155
readonly LAUNCHER=$(basename "${0}")
readonly DISK_MIN_SIZE=$((197632 * 8))
readonly VERSION="4.0"

# TODO: Make this run the native architecture binary
QEMU=$(command -v qemu-system-x86_64)
QEMU_IMG=$(command -v qemu-img)
if [ ! -e "${QEMU}" ] || [ ! -e "${QEMU_IMG}" ]; then
  echo "ERROR! QEMU not found. Please make install qemu-system-x86_64 and qemu-img"
  exit 1
fi

QEMU_VER_LONG=$(${QEMU} -version | head -n1 | cut -d' ' -f4 | cut -d'(' -f1)
QEMU_VER_SHORT=$(${QEMU} -version | head -n1 | cut -d' ' -f4 | cut -d'(' -f1 | sed 's/\.//g' | cut -c1-2)
if [ "${QEMU_VER_SHORT}" -lt 60 ]; then
  echo "ERROR! Qemu 6.0.0 or newer is required, detected ${QEMU_VER_LONG}."
  exit 1
fi

# Take command line arguments
if [ $# -lt 1 ]; then
    usage
    exit 0
else
    while [ $# -gt 0 ]; do
        case "${1}" in
          -braille|--braille)
            BRAILLE="on"
            shift;;
          -delete|--delete|-delete-disk|--delete-disk)
            DELETE_DISK=1
            shift;;
          -delete-vm|--delete-vm)
            DELETE_VM=1
            shift;;
          -display|--display)
            OUTPUT="${2}"
            display_param_check
            shift
            shift;;
          -fullscreen|--fullscreen|-full-screen|--full-screen)
            FULLSCREEN="-full-screen"
            FULLSPICY="--full-screen"
            shift;;
          -ignore-msrs-always|--ignore-msrs-always)
            ignore_msrs_always
            exit;;
          -screen|--screen)
            SCREEN="${2}"
            shift
            shift;;
          -snapshot|--snapshot)
            SNAPSHOT_ACTION="${2}"
            if [ -z "${SNAPSHOT_ACTION}" ]; then
              echo "ERROR! No snapshot action provided."
              exit 1
            fi
            shift
            SNAPSHOT_TAG="${2}"
            if [ -z "${SNAPSHOT_TAG}" ] && [ "${SNAPSHOT_ACTION}" != "info" ]; then
              echo "ERROR! No snapshot tag provided."
              exit 1
            fi
            shift
            shift;;
          -status-quo|--status-quo)
            STATUS_QUO="-snapshot"
            shift;;
          -shortcut|--shortcut)
            SHORTCUT=1
            shift;;
          -vm|--vm)
            VM="${2}"
            shift
            shift;;
          -viewer|--viewer)
            VIEWER="${2}"
            shift
            shift;;
          -ssh-port|--ssh-port)
            SSH_PORT="${2}"
            shift;
            shift;;
          -spice-port|--spice-port)
            SPICE_PORT="${2}"
            shift;
            shift;;
          -public-dir|--public-dir)
            PUBLIC="${2}"
            shift;
            shift;;
          -monitor|--monitor)
            MONITOR="${2}"
            shift;
            shift;;
          -monitor-cmd|--monitor-cmd)
            MONITOR_CMD="${2}"
            shift;
            shift;;
          -monitor-telnet-host|--monitor-telnet-host)
            MONITOR_TELNET_HOST="${2}"
            shift;
            shift;;
          -monitor-telnet-port|--monitor-telnet-port)
            MONITOR_TELNET_PORT="${2}"
            shift;
            shift;;
          -serial|--serial)
            SERIAL="${2}"
            shift;
            shift;;
          -serial-telnet-host|--serial-telnet-host)
            SERIAL_TELNET_HOST="${2}"
            shift;
            shift;;
          -serial-telnet-port|--serial-telnet-port)
            SERIAL_TELNET_PORT="${2}"
            shift;
            shift;;
          -keyboard|--keyboard)
            KEYBOARD="${2}"
            shift;
            shift;;
          -mouse|--mouse)
            MOUSE="${2}"
            shift;
            shift;;
          -usb-controller|--usb-controller)
            USB_CONTROLLER="${2}"
            shift;
            shift;;
          -extra_args|--extra_args)
            EXTRA_ARGS="${2}"
            shift;
            shift;;
          -version|--version)
            echo "${VERSION}"
            exit;;
          -h|--h|-help|--help)
            usage;;
          *)
            echo "ERROR! \"${1}\" is not a supported parameter."
            usage;;
        esac
    done
fi

if [ -n "${VM}" ] && [ -e "${VM}" ]; then
  # shellcheck source=/dev/null
  source "${VM}"
  if [ -z "${disk_img}" ]; then
    echo "ERROR! No disk_img defined."
    exit 1
  fi

  VMDIR=$(dirname "${disk_img}")
  VMNAME=$(basename "${VM}" .conf)
  VMPATH=$(realpath "$(dirname "${VM}")")
  VM_MONITOR_SOCKETPATH="${VMDIR}/${VMNAME}-monitor.socket"
  VM_SERIAL_SOCKETPATH="${VMDIR}/${VMNAME}-serial.socket"

  # Backwards compatibility for ${driver_iso}
  if [ -n "${driver_iso}" ] && [ -z "${fixed_iso}" ]; then
    fixed_iso="${driver_iso}"
  fi

  # Backwards compatibility for ${disk} (size)
  if [ -n "${disk}" ]; then
    disk_size="${disk}"
  fi

  # Set the default OUTPUT if not provided by user
  if [ -z "${OUTPUT}" ]; then
    OUTPUT="sdl"
  fi

  # Braille support requires SDL. Override OUTPUT if braille was requested.
  if [ -n "${BRAILLE}" ]; then
    OUTPUT="sdl"
  fi

  if [ -z "${VIEWER}" ]; then
    VIEWER="${viewer}"
  fi
  viewer_param_check

  # Set the default 3d acceleration. Will be overridden to "off" if
  # DISPLAY_DEVICE is determined to be virtio-vga since it doesn't work there.
  if [ -z "${gl}" ]; then
    gl="on"
  fi

  if [ -z "${PUBLIC}" ]; then
    PUBLIC="${public_dir}"
  fi

  if [ "${PUBLIC}" == "none" ]; then
    PUBLIC=""
  else
    # PUBLICSHARE is the only directory exposed to guest VMs for file
    # sharing via 9P, spice-webdavd and Samba. This path is not configurable.
    if [ -z "${PUBLIC}" ]; then
      if command -v xdg-user-dir &>/dev/null; then
        PUBLIC=$(xdg-user-dir PUBLICSHARE)
      fi
    fi

    if [ ! -d "${PUBLIC}" ]; then
      echo "ERROR! Public directory: '${PUBLIC}' doesn't exist!"
      exit 1
    fi

    PUBLIC_TAG="Public-${USER,,}"
    # shellcheck disable=SC2012
    PUBLIC_PERMS=$(ls -ld "${PUBLIC}" | cut -d' ' -f1)
  fi

  if [ -z "${SSH_PORT}" ]; then
    SSH_PORT=${ssh_port}
  fi
  if [ -n "${SSH_PORT}" ] &&  ! is_numeric "${SSH_PORT}"; then
    echo "ERROR: ssh-port must be a number!"
    exit 1
  fi

  if [ -z "${SPICE_PORT}" ]; then
    SPICE_PORT=${spice_port}
  fi
  if [ -n "${SPICE_PORT}" ] && ! is_numeric "${SPICE_PORT}"; then
    echo "ERROR: spice-port must be a number!"
    exit 1
  fi

  # Check if vm is already run
  VM_PID=0
  VM_UP=0
  if [ -r "${VMDIR}/${VMNAME}.pid" ]; then
    VM_PID=$(head -c50 "${VMDIR}/${VMNAME}.pid")
    kill -0 ${VM_PID} 2>&1 >/dev/null
    if [ $? -eq 0 ]; then
        echo "VM already started!"
        VM_UP=1
    fi
  fi

  if [ "${tpm}" == "on" ]; then
    SWTPM=$(command -v swtpm)
    if [ ! -e "${SWTPM}" ]; then
      echo "ERROR! TPM is enabled, but swtpm was not found."
      exit 1
    fi
  fi
else
  echo "ERROR! Virtual machine configuration not found."
  usage
fi

if [ ${DELETE_DISK} -eq 1 ]; then
  delete_disk
  exit
fi

if [ ${DELETE_VM} -eq 1 ]; then
  delete_vm
  exit
fi

if [ -n "${SNAPSHOT_ACTION}" ]; then
  case ${SNAPSHOT_ACTION} in
    apply)
      snapshot_apply "${SNAPSHOT_TAG}"
      snapshot_info
      exit;;
    create)
      snapshot_create "${SNAPSHOT_TAG}"
      snapshot_info
      exit;;
    delete)
      snapshot_delete "${SNAPSHOT_TAG}"
      snapshot_info
      exit;;
    info)
      snapshot_info
      exit;;
    *)
      echo "ERROR! \"${SNAPSHOT_ACTION}\" is not a supported snapshot action."
      usage;;
  esac
fi

if [ ${SHORTCUT} -eq 1 ]; then
  shortcut_create
  exit
fi

if [ $VM_UP -eq 0 ]; then
    vm_boot
    start_viewer
else
    parse_ports_from_file
    start_viewer
fi

[ -n "${MONITOR_CMD}" ] && monitor_send_cmd "${MONITOR_CMD}"

# vim:tabstop=2:shiftwidth=2:expandtab
